package com.msgqu.share.domain.pub;

/**
 * @description: 生成id工具类
 * @author: wsj
 * @create: 2020-11-12 10:19
 * @throws:
 */
public class SnowflakeId {

    /**开始时间戳*/
    private final long twepoch = 1577548800000L;
    /**工作ID所占位数*/
    private final long workerIdBits = 5L;
    /**数据标识id所占的位数*/
    private final Long datacenterIdsBits=5L;
    /**最大工作id*/
    private final long maxWorkId  = -1L ^ (-1L << workerIdBits);
    /**支持的最大数据标识id，31*/
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdsBits);
    /**序列在id中占的位数*/
    private final Long sequenceBits = 12L;
    /**机器向左移12位*/
    private final long workerIdShift = sequenceBits;
    /**数据标识id向左移17位*/
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    /**时间戳向左移17位*/
    private final long timestampLeftShift =  sequenceBits + workerIdBits + datacenterIdsBits;
    /**生成序列的掩码 4095*/
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
    /**工作机器id*/
    public long workerId;
    /**数据中心编号*/
    public long datacenterId;
    /**毫秒内序列*/
    private long sequence = 0L;
    /**上次生成id的时间戳*/
    private Long lastTimestamp = 1L;

    /**
     * @description: 要求分布式系统中服务器时间一致不得有、单节点部署分布情况下要保证数据区和机器编号不同，不然操作单张表容易出现主键冲突
     */
    public SnowflakeId(long workerId, long datacenterId){
        if(workerId > maxWorkId || workerId < 0){
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0",maxWorkId));
        }
        if(datacenterId > maxDatacenterId || datacenterId < 0){
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0",maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
        //this.lastTimestamp = getTimestamp();
    }

    public synchronized long nextId(){
        long timestamp = getTimestamp();
        //当前时间一定要大于上次生成id的时间，就是防止时钟重置
        if(timestamp < lastTimestamp){
            throw new RuntimeException(String.format("Clock moved backwards, refusing to generate id for %d millisconds",lastTimestamp-timestamp));
        }
        if(timestamp == lastTimestamp){
            sequence = (sequence + 1) & sequenceMask;
            //毫秒内序列溢出
            //阻塞到下一毫秒，获得新的时间戳
            if(sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        }else{
            sequence = 0L;
        }
        lastTimestamp = timestamp;
        return ((timestamp - twepoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift)
                | sequence;
    }

    private long getTimestamp() {
        return System.currentTimeMillis();
    }

    /**阻塞到下一个毫秒，直到获取新的时间戳*/
    private long tilNextMillis(long lastTimestamp){
        long timestamp = getTimestamp();
        while(timestamp <= lastTimestamp){
            timestamp = getTimestamp();
        }
        return timestamp;
    }
}
